#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/*
    ############################################################
    ###########################缓冲区###########################
    ############################################################
*/
/* #xxx# 的内容将先不在博客当中进行阐述 只是作为单纯的笔记即可*/
// int main() {
//   char* ptr = NULL;
//   const char* str1 = "hello fwrite\n";
//   const char* str2 = "hello write\n";

//   // 语言接口
//   printf("hello printf\n");
//   fprintf(stdout, "hello fprintf\n");
//   fwrite(str1, strlen(str1), 1, stdout);

//   // 系统调用接口
//   write(1, str2, strlen(str2));
//     /*
//         *ptr = "TEST";
//         此处演示程序崩溃是否会对缓冲区进行刷新(采用重定向的方式)
//         答案是并不会刷新缓冲区
//     */
//    // assert(ptr);
//   return 0;
//   /*
//   正常按顺序打印
//   $ ./mytest
//     hello printf
//     hello fprintf
//     hello fwrite
//     hello write

//   使用重定向>至log.txt文件后
//   再使用cat对文件进行显示时对应的打印的顺序将会出现变化

//     #这里变化的原因是因为实际上是存在两个缓冲区#
//     #分别为C语言标准库的缓冲区和操作系统层面的内核缓冲区#
//     #这里是因为上面三个接口函数是由C语言层面当中的缓冲区刷新至内核缓冲区#
//     #而write接口是由进程直接控制文件描述符进行写入的#
//     #并不进入C标准库缓冲区#
//     #同时在写入文件时是利用全缓冲为刷新条件#
//     ##全缓冲 -
//     缓冲区满或者进程退出进行刷新[这些缓冲指的是C标准库中的缓冲区刷新机制]##
//     ##行缓冲 - 当识别至换行时对缓冲区进行刷新##
//     ##不缓冲 - 不缓冲一般指当写入缓冲区时立即对缓冲区进行刷新##
//     #而write为系统调用接口 不需要经过语言级别的缓冲区#
//     #而在进程退出时程序将隐式调用exit
//     当执行exit时默认会去对标准输出缓冲区进行清理等操作#
//     #这个清理操作包含对缓冲区进行刷新#
//     -----
//     #例如空指针的非法解引用之类的崩溃不刷新#
//     ##类似于这种崩溃问题主要是触及到了操作系统的内存保护##
//     ##空指针所代表的区域本身就是一个不存在的区域
//     而这块区域当被解引用后将是一个无效的访问##
//     ##实际上就是页表中的虚拟内存存在 但并不存在真正的物理内存
//     所以这是一个非法的映射故为段错误##
//     ##空指针那部分的虚拟内存并未映射到任何有效物理内存当中##
//     ------
//     #assert断言虽然崩溃但是这个宏会调用abort abort可以使用man查询#
//     #abort会关闭所有打开的流并在关闭前刷新所有缓冲区#
//     ##abort()调用为C语言标准库所提供##

// */
// }

/////////////////////////////////

/* 带 fork() 的版本 */
// int main() {
//   // char* ptr = NULL;
//   const char* str1 = "hello fwrite\n";
//   const char* str2 = "hello write\n";

//   // 语言接口
//   printf("hello printf\n");
//   fprintf(stdout, "hello fprintf\n");
//   fwrite(str1, strlen(str1), 1, stdout);

//   // 系统调用接口
//   write(1, str2, strlen(str2));
//   fork();
//   return 0;
//   /*
//     直接运行程序后正常打印
//   $ ./mytest
//   hello printf
//   hello fprintf
//   hello fwrite
//   hello write
//   --------------
//   重定向至文件并使用cat显示文件信息
//   $ ./mytest >log.txt ;cat log.txt
//   hello write
//   hello printf
//   hello fprintf
//   hello fwrite
//   hello printf
//   hello fprintf
//   hello fwrite
//   发现当重定向至文件过后发现写入文件内的数据顺序与直接打印发生变化
//   且写入文件的数据数量发生变化 #这里共发生两种情况
//   分别为1.顺序问题 2.数据量问题# #1.顺序问题# #对于顺序问题而言与上文相同
//   write为系统调用接口将直接进行写入不经过C标准输出缓冲区#
//   #而其他调用将经过C标准输出缓冲区
//   又因为当进行重定向时刷新策略将变为块缓冲(全缓冲)#
//   #当进行块缓冲时则缓冲区满或者进程结束时才会将标准输出缓冲区中的数据刷新至内核缓冲区当中#
//   #故发生顺序问题#
//   ----------
//   #2.数据量问题#
//   #在该函数当中调用了一个fork()系统接口 这个接口是创建一个子进程#
//   #进程本身是其父进程的拷贝 这包括一部分的代码和数据#
//   #在问题1当中提到重定向时刷新策略将由打印的行缓冲变为块缓冲#
//   #而块缓冲是C标准输出缓冲区当缓冲区满时或者程序正常退出时刷新缓冲区#
//   #此时由于创建了子进程 子进程继承父进程的代码和数据#
//   #故子进程当中的C标准输出缓冲区也会存在一样的内容#
//   #而当进程结束过后 "return 0" 后编译器将会隐式调用一个exit函数
//   其中这个exit函数将去执行清理操作(包括刷新缓冲区)# #此时的内容
//   (父子进程各自的标准输出缓冲区) 将被最终写入至文件#
//   */
// }

/////////////////////////////////

/*调用系统接口close关闭文件描述符1版本*/
// int main() {
//   带换行'\n'
//    const char* str1 = "hello fwrite\n";
//    const char* str2 = "hello write\n";

//   // 语言接口
//   printf("hello printf\n");
//   fprintf(stdout, "hello fprintf\n");
//   fwrite(str1, strlen(str1), 1, stdout);

//   // 系统调用接口
//   write(1, str2, strlen(str2));
//   close(1);

  //---------------

  // 不带换行'\n'
//   const char* str1 = "hello fwrite";
//   const char* str2 = "hello write";

//   // 语言接口
//   printf("hello printf");
//   fprintf(stdout, "hello fprintf");
//   fwrite(str1, strlen(str1), 1, stdout);

//   // 系统调用接口
//   write(1, str2, strlen(str2));
//   close(1);

//   return 0;

//   /*
//   当每行打印时都存在 '\n' 时
//     正常执行程序将会按照顺序进行打印
//     $ ./mytest
//     hello printf
//     hello fprintf
//     hello fwrite
//     hello write
//     #原因是执行打印的时候采用的是行缓冲 每当遇到换行符时将刷新一次缓冲区#
//     #故将会按照顺序进行打印#
//     而重定向至文件后只会打印"hello write\n"

//     $ > log.txt ;./mytest >log.txt ;cat log.txt
//     hello write
//     #原因是当进行重定向后 行缓冲的刷新策略将被更新为全缓冲#
//     #write系统调用接口已经直接被进程通过内核缓冲区直接写入至文件当中#
//     #由于写入的数据并未完全写满同时进程暂未正常退出故C标准输出缓冲区的内容并未及时刷新至内核缓冲区当中#
//     #而在进程结束前调用了close()系统调用接口关闭了文件描述符1#
//     #关闭文件描述符后 "return 0" 后编译器隐式调用exit进行刷新缓冲区等清理操作#
//     #此时标准输出缓冲区的内容将被刷新至内核缓冲区当中#
//     #而此时的文件描述符1已经被关闭
//   内核缓冲区的内容将试图写入至文件描述符1对应的文件当中#
//     #但此时的文件描述符1已经被关闭 写入失败#
//     #故文件中只存在"hello write\n"

// //=================

//   当每行打印时不存在 '\n' 换行时
//     正常执行程序后只打印hello write
//     $ make clean;make;./mytest
//     rm -rf mytest
//     gcc -o mytest test.c -std=c99 -g -Wall
//     hello write
//     #在上文当中提到 正常写入至显示器时刷新策略采用的是行缓冲#
//     #而行缓冲为遇到换行时将会对缓冲区进行刷新#
//     #(除此之外其他情况与全缓冲策略相同)#
//     #而在写入过程中数据并不存在换行符且进程暂未退出#
//     #情况与上文当中的全缓冲的情况大致相同#
//     #即未被刷新至内核缓冲区当中且文件描述符1被关闭#
//     #不再赘述#

//     重定向至log.txt文件后与直接执行程序结果相同 只打印hello write
//     $ make clean;make;>log.txt ;./mytest >log.txt ;cat log.txt
//     rm -rf mytest
//     gcc -o mytest test.c -std=c99 -g -Wall
//     hello write
//     #原理与直接运行的原理相同 不再赘述#


//   */
// }


/////////////////////////

// int main()
// {
//   //
//   /*
//       //内核缓冲区是什么

//       在上文中普遍提到了内核缓冲区 那么什么是内核缓冲区?
//       实际上内核缓冲区是一个由操作系统提供的缓冲区
//       当一个进程每打开一个文件后 对应的OS将会为这个进程维护一个文件描述符
//       这个文件描述符中对应的指针指向的file结构体直接或间接地存储着该文件的各类数据
//       而操作系统OS将会根据这个文件的类型去为其分配相应类型的缓冲区
//       而由标准库中的缓冲区刷新到内核缓冲区这里的内核缓冲区将根据数据最终到达的文件类型从而经过不同的缓冲区
//   */

//   /*
//      缓冲区(用户层级)存在的意义是什么?
//      1.减少IO次数
//        通过在用户态积累数据,减少实际进行系统调用和内核态IO的次数
//      2.增加单次IO的数据吞吐量
//        通过批量的数据处理 提高每次IO操作的数据量从而提高IO效率
//      3.减少内核缓冲区的频繁操作
//        实际上内核缓冲区在进行IO操作时也涉及到资源的开销 而频繁IO必然会降低
//        整体的性能,而在用户态当中设置缓冲区可以减少实际内核态的IO次数从而提
//        高性能降低开销;
//      4.减少硬件损耗
//        实际上在进行IO操作的时候尤其是在内核态时最终都要于硬件进行交互,而频繁的
//        与硬件进行交互必然会使得硬件的使用寿命降低;
//        而用户态中的缓冲区可以通过减少IO次数使得减少内核态与硬件的交互从而延长硬
//        件的使用寿命;

//      这些优化措施共同作用,提升了系统的整体性能和资源利用率;

//   */
 
//  /*
//     格式化输出/输入
//     在此只对格式化输出进行解释
//     格式化输出例如printf fprintf等接口函数具体的功能是
//     1.解析格式字符串 识别出其中的格式说明符(例如%d %s等)
//     2.将字符串格式根据格式说明符进行格式化 转化为相应的字符串表示
//     3.将格式化后的字符串与格式字符串中的其他部分进行拼接 形成最终的输出字符串
//     4.将最终成型的输出字符串存储到用户态的缓冲区当中等待进一步处理(被输出至终端或是写入文件)

//  */

//   /*
//     用户态缓冲区的存在
//     fflush()函数是C语言提供的用于刷新用户态缓冲区数据内容的一个接口
//     使用man手册观察
//       #include <stdio.h>
//       int fflush(FILE *stream);
//     在传参当中只传入了一个FILE*类型的指针
//     而FILE类型实际上是一个结构体 而实际上这个结构体就封装了对应的用户态的缓冲区

//     typedef struct { //FILE结构的示例...  
//     int fd;             // 文件描述符
//     char *buffer;       // 指向缓冲区的指针
//     size_t buffer_size; // 缓冲区大小
//     size_t pos;         // 当前缓冲区中的位置
//     // 其他成员...
//     } FILE;

//     在这个结构当中关于缓冲区的指针只有一个char* buffer 
//     因为实际上缓冲区是通用的
//     无论是输入缓冲区还是输出缓冲区
//     真正决定缓冲区是输入还是输出取决于数据的I/O形式

//     对应的一个文件都只有一个缓冲区 
//     无论是用户态缓冲区还是内核态缓冲区
//     因为在用户态当中每当打开一个文件将会为这个文件维护一个FILE结构体
//     而在内核态当中 上文提到 操作系统将会根据文件的类型去为它分配一个对应的缓冲区
//     如果是严谨一点的话也可以说在C语言当中每个文件都存在两个缓冲区(用户态与内核态)

//   */
//   return 0;
// }


//------------------

/*
    fopen()的工作
    fopen()将调用系统调用接口open()使得操作系统为文件建立起内核上的结构
    并且调用malloc()为文件开辟一个FILE的结构体
    这个FILE结构体当中将存放这个文件对应的属性以及缓冲区

*/

//------------------

//演示文件的全缓冲刷新策略
int main(){
  // 带换行'\n'
   const char* str1 = "hello fwrite\n";
   const char* str2 = "hello write\n";

  // 语言接口
  printf("hello printf\n");
  fprintf(stdout, "hello fprintf\n");
  fwrite(str1, strlen(str1), 1, stdout);

  // 系统调用接口
  write(1, str2, strlen(str2));

  sleep(5);
  /*
      在这段代码当中使用了sleep(5)从而使得进程阻塞5s
      当时间结束后由于write()为系统调用接口不经过用户态缓冲区将立即刷新
      而在写入至文件时用户态的缓冲区采用的刷新策略为全缓冲(块缓冲)
      而全缓冲的策略为当缓冲区写满时或者是进程结束时才会刷新缓冲区
      而写入的内容并不多并不能写满用户态缓冲区故只能等待进程结束后再刷新至内核态缓冲区

      故能观察到当5s过去后优先写入文件的是write()接口的操作
      当进程结束后才能观察到用户态的C标准接口操作

      [使用脚本进行监测]
      while :; do cat log.txt ;sleep 1; echo "--------------" ;done

      [在另一个终端运行程序]
      $ >log.txt;make clean;make;./mytest >
      log.txt
      rm -rf mytest
      gcc -o mytest test.c -std=c99 -g -Wall

      [采用脚本检测的终端显示]
      --------------
      (省略)
      --------------
      --------------
      hello write
      (省略)
      --------------
      hello write
      --------------
      hello write
      hello printf
      hello fprintf
      hello fwrite
      --------------
  */
  return 0;
}